#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <string>
#include "Platform.h"
#include "Scintilla.h"
#include "StringCopy.h"
#include "CallTip.h"

#ifdef SCI_NAMESPACE
using namespace Scintilla;
#endif

CallTip::CallTip() {
  wCallTip = 0;
  inCallTipMode = false;
  posStartCallTip = 0;
  rectUp = PRectangle( 0, 0, 0, 0 );
  rectDown = PRectangle( 0, 0, 0, 0 );
  lineHeight = 1;
  offsetMain = 0;
  startHighlight = 0;
  endHighlight = 0;
  tabSize = 0;
  above = false;
  useStyleCallTip = false;
  insetX = 5;
  widthArrow = 14;
  borderHeight = 2;
  verticalOffset = 1;
  #ifdef __APPLE__
  colourBG = ColourDesired( 0xff, 0xff, 0xc6 );
  colourUnSel = ColourDesired( 0, 0, 0 );
  #else
  colourBG = ColourDesired( 0xff, 0xff, 0xff );
  colourUnSel = ColourDesired( 0x80, 0x80, 0x80 );
  #endif
  colourSel = ColourDesired( 0, 0, 0x80 );
  colourShade = ColourDesired( 0, 0, 0 );
  colourLight = ColourDesired( 0xc0, 0xc0, 0xc0 );
  codePage = 0;
  clickPlace = 0;
}

CallTip::~CallTip() {
  font.Release();
  wCallTip.Destroy();
}


static bool IsArrowCharacter( char ch ) {
  return ( ch == 0 ) || ( ch == '\001' ) || ( ch == '\002' );
}


bool CallTip::IsTabCharacter( char ch ) const {
  return ( tabSize > 0 ) && ( ch == '\t' );
}

int CallTip::NextTabPos( int x ) const {
  if( tabSize > 0 ) {
    x -= insetX;
    x = ( x + tabSize ) / tabSize;
    return tabSize * x + insetX;
  } else {
    return x + 1;
  }
}



void CallTip::DrawChunk( Surface *surface, int &x, const char *s,
                         int posStart, int posEnd, int ytext, PRectangle rcClient,
                         bool highlight, bool draw ) {
  s += posStart;
  int len = posEnd - posStart;
  int maxEnd = 0;
  const int numEnds = 10;
  int ends[numEnds + 2];
  for( int i = 0; i < len; i++ ) {
    if( ( maxEnd < numEnds ) &&
        ( IsArrowCharacter( s[i] ) || IsTabCharacter( s[i] ) ) ) {
      if( i > 0 ) {
        ends[maxEnd++] = i;
      }
      ends[maxEnd++] = i + 1;
    }
  }
  ends[maxEnd++] = len;
  int startSeg = 0;
  int xEnd;
  for( int seg = 0; seg < maxEnd; seg++ ) {
    int endSeg = ends[seg];
    if( endSeg > startSeg ) {
      if( IsArrowCharacter( s[startSeg] ) ) {
        xEnd = x + widthArrow;
        bool upArrow = s[startSeg] == '\001';
        rcClient.left = static_cast<XYPOSITION>( x );
        rcClient.right = static_cast<XYPOSITION>( xEnd );
        if( draw ) {
          const int halfWidth = widthArrow / 2 - 3;
          const int quarterWidth = halfWidth / 2;
          const int centreX = x + widthArrow / 2 - 1;
          const int centreY = static_cast<int>( rcClient.top + rcClient.bottom ) / 2;
          surface->FillRectangle( rcClient, colourBG );
          PRectangle rcClientInner( rcClient.left + 1, rcClient.top + 1,
                                    rcClient.right - 2, rcClient.bottom - 1 );
          surface->FillRectangle( rcClientInner, colourUnSel );
          if( upArrow ) {
            Point pts[] = {
              Point::FromInts( centreX - halfWidth, centreY + quarterWidth ),
              Point::FromInts( centreX + halfWidth, centreY + quarterWidth ),
              Point::FromInts( centreX, centreY - halfWidth + quarterWidth ),
            };
            surface->Polygon( pts, ELEMENTS( pts ), colourBG, colourBG );
          } else {
            Point pts[] = {
              Point::FromInts( centreX - halfWidth, centreY - quarterWidth ),
              Point::FromInts( centreX + halfWidth, centreY - quarterWidth ),
              Point::FromInts( centreX, centreY + halfWidth - quarterWidth ),
            };
            surface->Polygon( pts, ELEMENTS( pts ), colourBG, colourBG );
          }
        }
        offsetMain = xEnd;
        if( upArrow ) {
          rectUp = rcClient;
        } else
        { rectDown = rcClient; }
      } else if( IsTabCharacter( s[startSeg] ) ) {
        xEnd = NextTabPos( x );
      } else {
        xEnd = x + RoundXYPosition( surface->WidthText( font, s + startSeg, endSeg - startSeg ) );
        if( draw ) {
          rcClient.left = static_cast<XYPOSITION>( x );
          rcClient.right = static_cast<XYPOSITION>( xEnd );
          surface->DrawTextTransparent( rcClient, font, static_cast<XYPOSITION>( ytext ),
                                        s + startSeg, endSeg - startSeg,
                                        highlight ? colourSel : colourUnSel );
        }
      }
      x = xEnd;
      startSeg = endSeg;
    }
  }
}

int CallTip::PaintContents( Surface *surfaceWindow, bool draw ) {
  PRectangle rcClientPos = wCallTip.GetClientPosition();
  PRectangle rcClientSize( 0.0f, 0.0f, rcClientPos.right - rcClientPos.left,
                           rcClientPos.bottom - rcClientPos.top );
  PRectangle rcClient( 1.0f, 1.0f, rcClientSize.right - 1, rcClientSize.bottom - 1 );
  int ascent = RoundXYPosition( surfaceWindow->Ascent( font ) - surfaceWindow->InternalLeading( font ) );
  int ytext = static_cast<int>( rcClient.top ) + ascent + 1;
  rcClient.bottom = ytext + surfaceWindow->Descent( font ) + 1;
  const char *chunkVal = val.c_str();
  bool moreChunks = true;
  int maxWidth = 0;
  while( moreChunks ) {
    const char *chunkEnd = strchr( chunkVal, '\n' );
    if( chunkEnd == NULL ) {
      chunkEnd = chunkVal + strlen( chunkVal );
      moreChunks = false;
    }
    int chunkOffset = static_cast<int>( chunkVal - val.c_str() );
    int chunkLength = static_cast<int>( chunkEnd - chunkVal );
    int chunkEndOffset = chunkOffset + chunkLength;
    int thisStartHighlight = Platform::Maximum( startHighlight, chunkOffset );
    thisStartHighlight = Platform::Minimum( thisStartHighlight, chunkEndOffset );
    thisStartHighlight -= chunkOffset;
    int thisEndHighlight = Platform::Maximum( endHighlight, chunkOffset );
    thisEndHighlight = Platform::Minimum( thisEndHighlight, chunkEndOffset );
    thisEndHighlight -= chunkOffset;
    rcClient.top = static_cast<XYPOSITION>( ytext - ascent - 1 );
    int x = insetX;
    DrawChunk( surfaceWindow, x, chunkVal, 0, thisStartHighlight,
               ytext, rcClient, false, draw );
    DrawChunk( surfaceWindow, x, chunkVal, thisStartHighlight, thisEndHighlight,
               ytext, rcClient, true, draw );
    DrawChunk( surfaceWindow, x, chunkVal, thisEndHighlight, chunkLength,
               ytext, rcClient, false, draw );
    chunkVal = chunkEnd + 1;
    ytext += lineHeight;
    rcClient.bottom += lineHeight;
    maxWidth = Platform::Maximum( maxWidth, x );
  }
  return maxWidth;
}

void CallTip::PaintCT( Surface *surfaceWindow ) {
  if( val.empty() ) {
    return;
  }
  PRectangle rcClientPos = wCallTip.GetClientPosition();
  PRectangle rcClientSize( 0.0f, 0.0f, rcClientPos.right - rcClientPos.left,
                           rcClientPos.bottom - rcClientPos.top );
  PRectangle rcClient( 1.0f, 1.0f, rcClientSize.right - 1, rcClientSize.bottom - 1 );
  surfaceWindow->FillRectangle( rcClient, colourBG );
  offsetMain = insetX;
  PaintContents( surfaceWindow, true );
  #ifndef __APPLE__
  surfaceWindow->MoveTo( 0, static_cast<int>( rcClientSize.bottom ) - 1 );
  surfaceWindow->PenColour( colourShade );
  surfaceWindow->LineTo( static_cast<int>( rcClientSize.right ) - 1, static_cast<int>( rcClientSize.bottom ) - 1 );
  surfaceWindow->LineTo( static_cast<int>( rcClientSize.right ) - 1, 0 );
  surfaceWindow->PenColour( colourLight );
  surfaceWindow->LineTo( 0, 0 );
  surfaceWindow->LineTo( 0, static_cast<int>( rcClientSize.bottom ) - 1 );
  #endif
}

void CallTip::MouseClick( Point pt ) {
  clickPlace = 0;
  if( rectUp.Contains( pt ) ) {
    clickPlace = 1;
  }
  if( rectDown.Contains( pt ) ) {
    clickPlace = 2;
  }
}

PRectangle CallTip::CallTipStart( int pos, Point pt, int textHeight, const char *defn,
                                  const char *faceName, int size,
                                  int codePage_, int characterSet,
                                  int technology, Window &wParent ) {
  clickPlace = 0;
  val = defn;
  codePage = codePage_;
  Surface *surfaceMeasure = Surface::Allocate( technology );
  if( !surfaceMeasure ) {
    return PRectangle();
  }
  surfaceMeasure->Init( wParent.GetID() );
  surfaceMeasure->SetUnicodeMode( SC_CP_UTF8 == codePage );
  surfaceMeasure->SetDBCSMode( codePage );
  startHighlight = 0;
  endHighlight = 0;
  inCallTipMode = true;
  posStartCallTip = pos;
  XYPOSITION deviceHeight = static_cast<XYPOSITION>( surfaceMeasure->DeviceHeightFont( size ) );
  FontParameters fp( faceName, deviceHeight / SC_FONT_SIZE_MULTIPLIER, SC_WEIGHT_NORMAL, false, 0, technology, characterSet );
  font.Create( fp );
  int numLines = 1;
  const char *newline;
  const char *look = val.c_str();
  rectUp = PRectangle( 0, 0, 0, 0 );
  rectDown = PRectangle( 0, 0, 0, 0 );
  offsetMain = insetX;
  int width = PaintContents( surfaceMeasure, false ) + insetX;
  while( ( newline = strchr( look, '\n' ) ) != NULL ) {
    look = newline + 1;
    numLines++;
  }
  lineHeight = RoundXYPosition( surfaceMeasure->Height( font ) );
  int height = lineHeight * numLines - static_cast<int>( surfaceMeasure->InternalLeading( font ) ) + borderHeight * 2;
  delete surfaceMeasure;
  if( above ) {
    return PRectangle( pt.x - offsetMain, pt.y - verticalOffset - height, pt.x + width - offsetMain, pt.y - verticalOffset );
  } else {
    return PRectangle( pt.x - offsetMain, pt.y + verticalOffset + textHeight, pt.x + width - offsetMain, pt.y + verticalOffset + textHeight + height );
  }
}

void CallTip::CallTipCancel() {
  inCallTipMode = false;
  if( wCallTip.Created() ) {
    wCallTip.Destroy();
  }
}

void CallTip::SetHighlight( int start, int end ) {
  if( ( start != startHighlight ) || ( end != endHighlight ) ) {
    startHighlight = start;
    endHighlight = ( end > start ) ? end : start;
    if( wCallTip.Created() ) {
      wCallTip.InvalidateAll();
    }
  }
}



void CallTip::SetTabSize( int tabSz ) {
  tabSize = tabSz;
  useStyleCallTip = true;
}



void CallTip::SetPosition( bool aboveText ) {
  above = aboveText;
}



void CallTip::SetForeBack( const ColourDesired &fore, const ColourDesired &back ) {
  colourBG = back;
  colourUnSel = fore;
}
